package com.mindata.blockchain.core.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.mindata.blockchain.block.BlockBody;
import com.mindata.blockchain.block.Instruction;
import com.mindata.blockchain.block.InstructionReverse;
import com.mindata.blockchain.block.Operation;
import com.mindata.blockchain.block.queue.InstructionSender;
import com.mindata.blockchain.block.queue.SenderCallBack;
import com.mindata.blockchain.common.BlockUtils;
import com.mindata.blockchain.common.CommonUtil;
import com.mindata.blockchain.common.ResultPack;
import com.mindata.blockchain.common.Sha256;
import com.mindata.blockchain.common.SystemConfig;
import com.mindata.blockchain.common.TrustSDK;
import com.mindata.blockchain.common.exception.TrustSDKException;
import com.mindata.blockchain.core.event.AddInstructionEvent;
import com.mindata.blockchain.core.manager.DbBlockManager;
import com.mindata.blockchain.core.requestbody.InstructionBody;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;

/**
 * 一条指令的service
 *
 * @author wuweifeng wrote on 2018/3/7.
 */
@Service
@Slf4j
public class InstructionService {

    private InstructionSender instructionSender;

    @Resource
    private BlockService blockService;
    @Resource
    private DbBlockManager dbBlockManager;
    @Resource
    private SystemConfig systemConfig;


    /**
     * 初始化指令池
     */
    @PostConstruct
    public void initInstructionSender() {
        instructionSender = new InstructionSender("instructionPool", systemConfig.getMaxPoolSize(), systemConfig.getPackageLimitSize(), new SenderCallBack() {

            /**
             * 队列消费处理
             *
             * @param instructions
             */
            @Override
            public void consume(List<Instruction> instructions) {
                List<Instruction> appendingInstructions = dbBlockManager.checkAndGetpendingInstructions(instructions);
                log.info("队列消费处理指令数:{} Appending数:{}", instructions.size(), appendingInstructions.size());
                if (CollUtil.isNotEmpty(appendingInstructions)) {
                    BlockBody blockBody = new BlockBody();
                    blockBody.setInstructions(appendingInstructions);
                    blockService.addBlock(blockBody);
                }
            }
        });
    }

    /**
     * 广播指令加入指令池队列
     *
     * @param addInstructionEvent
     */
    @EventListener(AddInstructionEvent.class)
    public void addInstruction(AddInstructionEvent addInstructionEvent) {
        List<Instruction> instructions = addInstructionEvent.getSource();
        List<Instruction> appendingInstructions = dbBlockManager.checkAndGetpendingInstructions(instructions);
        log.info("广播接受指令数:{} Appending数:{}", instructions.size(), appendingInstructions.size());

        if (CollUtil.isNotEmpty(appendingInstructions)) {
            appendingInstructions.forEach(e -> instructionSender.production(e));
        }
    }

    /**
     * 校验公私钥是不是一对
     *
     * @param instructionBody instructionBody
     * @return boolean
     * @throws TrustSDKException TrustSDKException
     */
    public boolean checkKeyPair(InstructionBody instructionBody) throws TrustSDKException {
        return TrustSDK.checkPairKey(instructionBody.getPrivateKey(), instructionBody.getPublicKey());
    }

    /**
     * 校验内容的合法性
     *
     * @param instructionBody instructionBody
     * @return true false
     */
    public ResultPack<String> checkContent(InstructionBody instructionBody) {
        try {
            if (!this.checkKeyPair(instructionBody)) {
                return ResultPack.failed("公私钥不是一对");
            }
        } catch (TrustSDKException e) {
            log.error("checkKeyPair error", e);
            return ResultPack.failed(e.getMessage());
        }
        Operation operation = Operation.fromValue(instructionBody.getOperation());
        if (operation == null) {
            return ResultPack.failed("非法operation:" + instructionBody.getOperation());
        }
        String json = instructionBody.getJson();
        String oldJson = instructionBody.getOldJson();
        //不是add时，必须要有id和json和原始json
        if (Operation.ADD != operation && (StringUtils.isEmpty(instructionBody.getInstructionId())
                || StringUtils.isEmpty(json) || StringUtils.isEmpty(oldJson))) {
            return ResultPack.failed("Delete和Update操作需要有id和json内容");
        }
        //校验json是否合法
        if (StringUtils.isNotBlank(json) && !JSONUtil.isJson(json)) {
            return ResultPack.failed("json格式非法");
        }
        if (StringUtils.isNotBlank(oldJson) && !JSONUtil.isJson(oldJson)) {
            return ResultPack.failed("oldJson格式非法");
        }
        return ResultPack.succeed();
    }

    /**
     * 根据传来的body构建一条指令
     *
     * @param instructionBody body
     * @return Instruction
     */
    public Instruction build(InstructionBody instructionBody) throws Exception {
        Instruction instruction = new Instruction();
        BeanUtil.copyProperties(instructionBody, instruction);
        Operation operation = Operation.fromValue(instruction.getOperation());
        if (Operation.ADD == operation) {
            instruction.setInstructionId(CommonUtil.generateUuid());
        }
        instruction.setTimeStamp(CommonUtil.getNow());
        String buildStr = BlockUtils.getInstructionSignStr(instruction);
        //设置签名，供其他人验证
        instruction.setSign(TrustSDK.signString(instructionBody.getPrivateKey(), buildStr));
        //设置hash，防止篡改
        instruction.setHash(Sha256.sha256(buildStr));

        return instruction;
    }


    /**
     * 根据一个指令，计算它的回滚时的指令。<p>
     * 如add table1 {id:xxx, name:"123"}，那么回滚时就是delete table1 {id:xxx}
     * 如delete table2 id2 oldJson:{id:xxx, name:"123"}，那么回滚时就是add table2 {id:xxx, name:"123"}。
     * 如update table3 id3 json:{id:xxx, name:"123"} oldJson:{id:xxx, name:"456"}
     * 注意，更新和删除时，原来的json都得有，不然没法回滚
     *
     * @param instruction instruction
     * @return 回滚指令
     */
    public InstructionReverse buildReverse(Instruction instruction) {
        InstructionReverse instructionReverse = new InstructionReverse();
        BeanUtil.copyProperties(instruction, instructionReverse);
        Operation operation = Operation.fromValue(instruction.getOperation());

        if (Operation.ADD == operation) {
            instructionReverse.setOperation(Operation.DELETE.getKey());
            instructionReverse.setExecJson(instruction.getJson());
        } else if (Operation.DELETE == operation) {
            instructionReverse.setOperation(Operation.ADD.getKey());
            instructionReverse.setExecJson(instruction.getOldJson());
        } else if (Operation.UPDATE == operation) {
            instructionReverse.setExecJson(instruction.getOldJson());
        }

        return instructionReverse;
    }

    public boolean checkSign(Instruction instruction) throws TrustSDKException {
        String buildStr = BlockUtils.getInstructionSignStr(instruction);
        return TrustSDK.verifyString(instruction.getPublicKey(), buildStr, instruction.getSign());
    }

    public boolean checkHash(Instruction instruction) {
        String buildStr = BlockUtils.getInstructionSignStr(instruction);
        return Sha256.sha256(buildStr).equals(instruction.getHash());
    }

    /**
     * 增加指令到指令池
     *
     * @param instruction
     * @return
     */
    public ResultPack<String> addPendingInstructions(Instruction instruction) {
        return instructionSender.production(instruction);
    }
}
